from functools import cached_property
from typing_extensions import Self
from pydantic import BaseModel  # pylint: disable=E0611


class Interval(BaseModel):
    chrom: str
    start: int
    end: int
    name: str

    @cached_property
    def size(self) -> int:
        return self.end - self.start

    @classmethod
    def from_bed_line(cls, line: str) -> Self:
        row = line.strip().split("\t")
        name = row[3] if len(row) == 4 else ":".join(row)
        return cls(chrom=row[0], start=int(row[1]), end=int(row[2]), name=name)

    @classmethod
    def from_cytoband_line(cls, line: str) -> Self:
        row = line.strip().split("\t")
        return cls(chrom=row[0], start=int(row[1]), end=int(row[2]), name=row[3])


class Transcript(BaseModel):
    name: str
    chrom: str
    strand: str
    tx_start: int
    tx_end: int
    cds_start: int
    cds_end: int
    exon_count: int
    exon_starts: list[int]
    exon_ends: list[int]
    gene: str

    @cached_property
    def gene_pk(self) -> str:
        return f"{self.chrom}:{self.gene}"

    @cached_property
    def is_mrna(self) -> str:
        return "N" if self.cds_start == self.cds_end else "Y"

    @cached_property
    def intron_starts(self) -> list[Interval]:
        return self.exon_ends[:-1]

    @cached_property
    def intron_ends(self) -> list[Interval]:
        return self.exon_ends[1:]

    @cached_property
    def exons(self) -> list[Interval]:
        tag = 1 if self.strand == "+" else -1
        return list(
            map(
                lambda x: Interval(chrom=self.chrom, start=x[1][0], end=x[1][1], name=f"exon{x[0] + 1}"),
                enumerate(
                    zip(self.exon_starts[::tag], self.exon_ends[::tag]),
                ),
            )
        )

    @cached_property
    def introns(self) -> list[Interval]:
        tag = 1 if self.strand == "+" else -1
        return list(
            map(
                lambda x: Interval(chrom=self.chrom, start=x[1][0], end=x[1][1], name=f"intron{x[0] + 1}"),
                enumerate(zip(self.intron_starts[::tag], self.intron_ends[::tag])),
            )
        )

    def calc_detail(self, start: int, end: int) -> str:
        regions = list(
            filter(
                lambda x: x.start < end and x.end > start,
                sorted(self.exons + self.introns, key=lambda x: x.start, reverse=self.strand == "-"),
            )
        )
        region = regions[0].name if len(regions) == 1 else f"{regions[0].name}_{regions[1].name}"
        return f"{self.gene}:{self.name}:{region}:{self.is_mrna}"

    @classmethod
    def from_genepred_line(cls, line: str) -> Self:
        row = line.strip().split("\t")
        return cls(
            name=row[1],
            chrom=row[2],
            strand=row[3],
            tx_start=int(row[4]),
            tx_end=int(row[5]),
            cds_start=int(row[6]),
            cds_end=int(row[7]),
            exon_count=int(row[8]),
            exon_starts=list(map(int, row[9].strip(",").split(","))),
            exon_ends=list(map(int, row[10].strip(",").split(","))),
            gene=row[12],
        )
